home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 5 / Apprentice-Release5.iso / Environments / Oberon⁄F™ 1.2 / Preinstalled version / Manuals / Concepts (.txt) < prev    next >
Encoding:
Oberon Document  |  1996-07-08  |  73.8 KB  |  436 lines  |  [oODC/obnF]

  1. Documents.StdDocumentDesc
  2. Documents.DocumentDesc
  3. Containers.ViewDesc
  4. Views.ViewDesc
  5. Stores.StoreDesc
  6. Documents.ModelDesc
  7. Containers.ModelDesc
  8. Models.ModelDesc
  9. Stores.ElemDesc
  10. TextViews.StdViewDesc
  11. TextViews.ViewDesc
  12. TextModels.StdModelDesc
  13. TextModels.ModelDesc
  14. TextModels.AttributesDesc
  15. Helvetica
  16. TextRulers.StdRulerDesc
  17. TextRulers.RulerDesc
  18. TextRulers.StdStyleDesc
  19. TextRulers.StyleDesc
  20. TextRulers.AttributesDesc
  21. StdLogos.ViewDesc
  22. Helvetica
  23. Helvetica
  24. Helvetica
  25. StdFolds.FoldDesc
  26. Helvetica
  27. StdLinks.LinkDesc
  28. StdCmds.OpenBrowser('Manuals/Copyrigh', 'Copyright')
  29. Helvetica
  30. Oberon/F Release 1.2
  31. Copyright 
  32.  1994-1996 by Oberon microsystems, Inc., Switzerland.
  33. All rights reserved. No part of this publication may be reproduced in any form or by any means, without prior written permission by Oberon microsystems. The only exception is the free electronic distribution of the education version of Oberon/F (see the accompanying 
  34. copyright
  35. notice
  36.  for details).
  37. Oberon/F module interfaces and their descriptions in particular may not be used in other works without written permission.
  38. Oberon microsystems, Inc.
  39. Technoparkstrasse 1
  40. CH-8005 Z
  41. Switzerland
  42. Oberon is a trademark of ETH Z
  43. rich, Switzerland.
  44. Oberon/F, Oberon/L, "Oberon by Example", "The Oberon Tribune", "Oberon Developer Forum", and "Drag & Pick" are trademarks of Oberon microsystems, Inc.
  45. All other trademarks and registered trademarks belong to their respective owners.
  46. Helvetica
  47. StdLinks.ShowTarget('From Applications')
  48. Helvetica
  49. StdLinks.ShowTarget('The Module')
  50. StdLinks.ShowTarget('Subsystems and')
  51. StdLinks.ShowTarget('Device Drivers')
  52. StdLinks.ShowTarget('User Interaction')
  53. StdLinks.ShowTarget('Message Records')
  54. StdLinks.ShowTarget('Garbage Collection')
  55. StdLinks.ShowTarget('The Architect')
  56. StdLinks.ShowTarget('From White')
  57. Helvetica
  58. StdLinks.TargetDesc
  59. From Applications
  60. Helvetica
  61. HostPictures.StdViewDesc
  62. Geneva
  63. Application
  64. Library
  65. y    Extension
  66. u    Framework
  67. The Module
  68.     Helvetica
  69. Controllers
  70. Dialog
  71. Views
  72. Models
  73. Stores
  74. Files
  75. Fonts
  76. Ports
  77. Domains
  78. Properties
  79. Containers
  80. Controls
  81. Services
  82. Strings
  83. Dates
  84. Library
  85.     Framework
  86. Converters
  87. Subsystems
  88. Helvetica
  89. Subsystems and
  90. Device Drivers
  91. Geneva
  92. RS232Rider
  93. RS232Rider
  94. RS232Carrier
  95. NetRider
  96. NetRider
  97. NetCarrier
  98. Geneva
  99.     BinMapper
  100. ASCIIMapper
  101. NetRider
  102. NetRider
  103. NetCarrier
  104. Geneva
  105. mapper
  106. rider
  107. Binary
  108. Mapper
  109. ASCII
  110. Mapper
  111. RS232Rider
  112. NetRider
  113. Binary
  114. on RS232
  115. ASCII
  116. on RS232
  117. Binary
  118. on net
  119. ASCII
  120. on net
  121. User Interaction
  122. Geneva
  123. model
  124. controller
  125. controller
  126. Geneva
  127. window
  128. child window
  129.     text view
  130. text model
  131. graphic view
  132. frame for text view
  133. frame for (
  134. graphic view
  135. frame for (
  136. graphic view
  137. graphic model
  138. graphic view
  139. Message Records
  140. Geneva
  141. front path
  142. target path
  143. front focus
  144. target focus
  145. front window
  146. bottom window
  147. focus hierarchy
  148. Geneva
  149. model
  150. frame
  151. frame
  152. frame
  153. frame
  154. model notifies its
  155. views with a model
  156.     broadcast
  157. views notify their
  158. frames with view
  159. broadcasts
  160. Garbage Collection
  161. The Architect
  162. Geneva
  163. Fonts
  164. Stores
  165. Models
  166. Views
  167. Controllers
  168. From White
  169.  Oberon/F
  170. The Concepts Behind Oberon/F
  171. Copyright Notice
  172. Contents
  173. Applications
  174. Frameworks
  175. Components
  176. Module
  177. Hierarchies
  178. Oberon/F
  179. Subsystems
  180. Global
  181. Modules
  182. Device
  183. Drivers:
  184. Carriers,
  185. Riders,
  186. Mappers,
  187. Directories
  188. Interaction:
  189. Models,
  190. Views,
  191. Controllers
  192. Message
  193. Records
  194. Garbage
  195. Collection
  196. Prerequisite
  197. Safety
  198. Architect's
  199. Tools
  200. Trade
  201. White-Box
  202. Frameworks
  203. Towards
  204. Black-Box
  205. Frameworks
  206. The first three sections of this chapter serve as an introduction to and overview over the design of Oberon/F. They are recommended reading for anyone who wants to get a deeper understanding of Oberon/F.
  207. The next three sections give an in-depth description of several important concepts of Oberon/F.  It is recommended that the reader already has gained some experience with Oberon/F before reading these sections; e.g. by  reading the tutorials, or by working through the Obx examples.
  208. The last three sections give a more theoretical background to some fundamental concepts on which Oberon/F rests.
  209. From Applications to Frameworks and Components
  210. An application framework is a semi-finished product. It implements generic parts of an application, i.e. parts which are the same - or at least similar - for every similar kind of application. For graphical user interfaces, these are mainly the management of windows, menus, printing, and other generic services. A programmer need only provide the rest of the application, namely the parts which are unique to it. This is termed extending the framework.
  211. In a component software environment, framework extensions are components which can be loaded dynamically; they are not pre-linked into the framework. From a user's perspective, components are represented e.g. as menus or as document parts. From a programmer's perspective, components are modules or whole subsystems.
  212. One potential benefit of using an application framework becomes immediately clear when we look at the source code for a graphical user interface (GUI) program that was written completely from scratch: it can take pages of code merely to initialize and open a window with its menus. Frameworks dramatically reduce the effort to write GUI applications by eliminating such chores.
  213. In contrast to subroutine libraries, a framework allows much more than mere code reuse. It defines a structure for the framework's extensions, i.e. a blue-print which can be followed when creating a new extension component.
  214. The structure of a framework extension is predefined by the framework's interfaces. Extensions must provide suitable implementations of these interfaces. The interfaces of a framework, their documentation, and the default code contained in the framework are the means by which a framework defines a prefabricated structure for future extension components.
  215. In contrast to classical subroutine libraries, a framework is not only called by some application, but the framework also calls the application. A call from framework to application (i.e. extension component) is called an "upcall". Why this term is justified can be seen in the following diagram, where an arrow describes who calls whom:
  216. Picture a : Typical Control Flows in a Subroutine
  217. Library and in an Application Framework
  218. Sometimes this is called the "Hollywood Principle" of object-oriented programming, which says:
  219. Don't call us, we call you.
  220. In reality, the situation is more involved. An extension sometimes does call the framework, an extension can itself be extensible and act as a more specialized framework, and several extensions may work together "sideways" as well. These potentially confusing possibilities underline the importance of structure, which is afforded by the framework. For large software systems, a good system structure (sometimes called a system design or system architecture) becomes far more important than the issue of code reuse, for example. In practice, a bad system structure quickly limits extensibility and integration.
  221. Oberon/F is a component framework because it can be extended at run-time by new components. Consequently, there is no single application in the traditional sense anymore. Rather, there is only an ever growing collection of interacting components. The main challenge in the design of a component framework is how to support close integration of components, while at the same time ensuring cooperation and robustness.
  222. The Module and Type Hierarchies of Oberon/F
  223. Oberon/F is decomposed into a hierarchy of modules which provide systematically constructed abstractions. Features which are not used can be ignored ("progressive disclosure").
  224. At the "bottom" of Oberon/F, the differences between hardware platforms are hidden as much as possible. At the "top", the differences between user-interfaces are hidden as much as possible. For example, there are no assumptions built into Oberon/F about the underlying window system, e.g. whether it provides overlapping or tiling windows. In fact, the abstraction of a window is just as absent from Oberon/F as the abstraction of an application.
  225. Because Oberon/F constitutes a layer between the operating system and the application, it must be efficient. This not only applies to speed, but also to memory and disk requirements. However, the efficiency of a particular Oberon/F implementation is necessarily dependent on the quality of the underlying operating system and hardware.
  226. Oberon/F interfaces are safe, i.e. an Oberon/F application which doesn't use the low-level facilities of module SYSTEM is type-safe. A module which is type-safe may only create local damage, even if it contains arbitrary errors. In particular, it cannot destroy invariants guaranteed by other modules. This bug containment is especially relevant if components of differing origins are combined in a single document.
  227. A central aspect of Oberon/F is its extensibility. It is possible to extend most major Oberon/F data types, in order to add new functionality to the existing system. Many basic services can be replaced or supplemented by new customized versions, in a way that existing applications can make use of the new facilities. To reach this degree of extensibility and configurability, module interfaces are constructed in a systematic way, which is described in this chapter.
  228. For the design of the Oberon/F interfaces, several principles have been followed:
  229. Simplicity
  230. It should be easy to learn and easy to use the module interfaces.
  231. Safety
  232. Interfaces should be safe, i.e. the integrity of the system must be guaranteed as long as only public interfaces are used; even if they are used in arbitrarily incorrect ways.
  233. Interfaces as Contracts
  234. Interfaces should be specified in a sufficiently precise way, using preconditions, postconditions, and sometimes equivalent source code fragments. Violations of preconditions or other programming errors should be detected and flagged as early as possible.
  235. Portability
  236. The services provided by public interfaces should be implementable on every modern personal computer or workstation.
  237. Separation of User Interface and Program
  238. User interface and program should be coupled as loosely as possible. A program should not be burdened with handling details of the user interface. It should be possible to modify user interfaces without changing a program's source code, and without recompilation.
  239. Hiding Global Resources
  240. Global resources (e.g. files, windows, menus) should not occur in public interfaces whenever possible. The is because different platforms and emerging compound document standards (OLE, OpenDoc) sometimes have their own strategies of how to manage and arbitrate the use of global shared resources. There should be as little interference with such strategies as possible.
  241. Picture a:  Module Hierarchy of Oberon/F
  242. The diagram above shows the module structure of Oberon/F. A small labeled rectangle denotes a module. A module which (may) import another one is placed higher in the hierarchy. A large labeled rectangle denotes a complete subsystem, consisting of several related modules.
  243. The hierarchy is structured into three layers. At the bottom, the library layer contains modules which provide a number of unrelated services:
  244. Services provides low-level system services. Currently, it mainly provides actions, i.e. atomic commands which can be executed in the background.
  245. Meta provides access to run-time type information (metaprogramming).
  246. Math is a collection of mathematical functions.
  247. Strings is a collection of string operations, plus conversions between numbers and strings.
  248. Dates provides date and time data types, operations on these data types, and conversion routines.
  249. Dialog provides various facilities for the interaction with the user, e.g. standard dialogs for file opening and closing, and for presenting error messages.
  250. Files provides abstract files of a hierarchical file system, and readers and writers as access paths to files.
  251. The framework layer contains services for display, input, document storage, and document-centric user interactions:
  252. Fonts handles font identification, font measurements, and font lookup.
  253. Ports provides abstract display ports, riders as access paths to ports, and frames as port mappers.
  254. Domains defines a grouping construct for arbitrary objects. This construct is called a domain. As an example, every displayed document belongs to one domain. Most message broadcasts are restricted to one domain.
  255. Stores defines the base type for all extensible storable objects, and file mappers for the externalization and internalization of stores.
  256. Converters provides a mechanism for the conversion between file formats and stores.
  257. Models defines a base type for the representation of storable data, as well as operations for the management of model modifications.
  258. Views defines the central abstraction of a view, which provides presentation of a model. 
  259. Controllers defines the controller abstraction. A controller supports user interaction with a view and the view's model.
  260. Properties defines the property abstraction. Properties allow to modify a view's attributes in a generic, and possibly non-interactive way.
  261. Containers defines model, view, and controller for general containers, i.e. for views which may contain a variable number of arbitrary other views. This fundamental abstraction allows to hide the major differences between the OLE and OpenDoc user interfaces.
  262. Controls gives a property interface to controls, i.e. to views such as buttons, check boxes, text entry fields, etc.
  263. Finally, the application layer provides three standard subsystems: a text subsystem, a subsystem for forms (e.g. dialogs), and a subsystem which constitutes the development environment. Miscellaneous other modules in the application layer are not shown here.
  264. Modules which are not of general interest or do not provide portable abstractions are called private. Their interfaces may not be portable, may not be documented, or may change arbitrarily between releases. Most names of private modules start with the prefix "Host" or "Std", which are reserved subsystem prefixes. An implementation may contain further private global modules like Kernel, Loader, Windows, or Documents.
  265. Private modules are not shown in the above module diagram. Some of them may be described in the host-specific documentation for a given Oberon/F implementation, but should not be considered as parts of Oberon/F proper. This also holds for interface modules to the host platform; which may be provided as well, in order to allow direct access to features particular to the given platform.
  266. An Oberon/F developer can use his own unique module name prefix for the module names of his project(s), in order to eliminate name clashes with module names of other developers. Oberon microsystems acts as a clearinghouse for such name prefixes, by providing a prefix registration service.
  267. Subsystems and Global Modules
  268. A potential problem for any independently extensible system is the name space pollution problem of extensions: if two vendors would sell different extensions for Oberon/F, but under the same name, a conflict could arise: the two extensions could not be used on the same machine at the same time, since Oberon/F wouldn't know which was which.
  269. It is possible to resolve such a conflict by appropriate renaming; however this requires that all clients of the renamed extension are updated accordingly; often an impossible task.
  270. A better solution is to avoid conflicts right from the start, by giving extensions globally unique names. This can be done by providing a central registration service for all extensions, in this case for all Oberon/F modules. To reduce the administrative overhead of such a solution, Oberon microsystems opted for a two-level strategy: Oberon microsystems only registers subsystem names for a small fee, guaranteeing that such a name hasn't been registered yet. The buyer of a subsystem name should then guarantee that no two modules in this subsystem have the same name, where subsystem simply means a collection of modules. Typically, a subsystem also corresponds to a project.
  271. This strategy is realized by a naming convention: every module name may contain a subsystem name as prefix, e.g. TextModels and TextViews belong to subsystem Text. The subsystem name begins with the first letter of the module name, and ends at the transition of a lower case letter (or digit) to an upper case letter. If no such transition exists, e.g. as in Files or Views, the module is called a global module, and belongs to the default subsystem called System. Global module names are reserved by Oberon microsystems. However, global module names may also be used by anyone for modules which are not meant to be published, i.e. where module name clashes can be resolved locally (e.g. when doing strictly in-house development and use of software) or where they are not critical (e.g. in a teaching environment).
  272. This naming convention is technically supported by Oberon/F in that its compiler, linker, loader, and other development tools use subsystem names to determine where to fetch (or put) symbol files (or code files), as well as documentation and resource files. In the Oberon/F directory, there is one directory for every subsystem. The standard subsystems are Dev, Form, Host, Mac (Mac OS), Obx, Std, System, and Text. The directory names must be identical to the subsystem names. A typical subsystem directory itself contains five directories: Code, Docu, Mod, Rsrc, and Sym.
  273. - the Code directory contains the code files of all modules in the subsystem, one file per module
  274. - the Docu directory may contain a programmer's reference for several or all modules in the subsystem, one file per module
  275. - the Mod directory may contain the sources for several or all modules in the subsystem, one file per module
  276. - the Rsrc directory may contain data files ("resources") for the whole subsystem, currently these are mostly Strings files, one per subsystem; and possibly some files containing forms (dialogs).
  277. - the Sym directory contains the symbol files for several or all modules in the subsystem, one file per module. A symbol file is a binary representation of a module's interface.
  278. In addition, there is typically a Map file in the subsystem directory, which leads to various documentation texts, e.g. one file per module in the Docu directory. Furthermore, a file Guide in the Docu directory is the user manual for this subsystem, and a file Flyer in the same directory is a product description.
  279. The Oberon compiler consumes program sources (usually the one displayed in the top window) and symbol files. A symbol file for module SubsystemModule is looked up as Subsystem/Sym/Module. The code file produced by the compiler is registered as Subsystem/Code/Module.
  280. Windows:
  281. The file extension of a symbol file is ".osf" (Oberon Symbol File).
  282. The file extension of a code file is ".ocf" (Oberon Code File).
  283. The browser (i.e. Info->Interface) looks up a symbol file for SubsystemModule as Subsystem/Sym/Module.
  284. The reference tool (i.e. Info->Source and Info->Documentation) look up Subsystem/Mod/Module, and Subsystem/Docu/Module, respectively.
  285. Note that files are only looked up, but not searched: a file's location is strictly determined by the module name (including the subsystem prefix) and by the kind of file to be looked up. If the file isn't at the expected location, lookup fails; no further search is performed.
  286. The only exception to this rule occurs with global modules. Their code files are always placed in the Code directory in the Oberon/F directory. Their symbol files are always placed in the Sym directory in the Oberon directory. However, if the lookup of either a code or a symbol file of a global module fails, another attempt is made to open the file in System/Code or System/Sym, respectively.
  287. If one of the development tools tries to register a file (e.g. a symbol file) in a directory which doesn't exist, the directory is created. The user is asked whether this should be done, or whether the command should be aborted instead.
  288. Windows:
  289. On file systems with restricted file name length (8 characters on Windows 3.1 or Windows for Workgroups), the module names are truncated accordingly. It is the programmer's responsibility to choose the module names such that the resulting file names differ. If files are transferred between a restricted and a less restricted file system (e.g. from Windows 3.1 to Windows NT or Mac OS), the file names must be adjusted.
  290. Collecting all files of a subsystem in one subdirectory is a natural way to organize projects. Since subsystems are often the "unit of extension", it makes sense to use a corresponding subdirectory as the "unit of distribution". In particular, this makes installation trivial: copy the subdirectory into the Oberon directory, and you're done. To deinstall a subsystem, simply delete its subdirectory.
  291. Note, however, that this only works if (mutable) data files are kept away from these subdirectories; otherwise deinstallation would result in a loss of data.
  292. Device Drivers: Carriers, Riders, Mappers, and Directories
  293. This section describes the strategy which is used for modeling device drivers in Oberon/F, as well as data structures which can be accessed in a similar way as devices, e.g. texts.
  294. Access to a device's data passes through two different objects: a rider and a carrier. A carrier represents a data container, e.g. a file or a pixelmap. Several access paths may be open on one carrier at the same time. This n:1 relationship gives rise to the separation of carriers and riders, where a rider represents one access path to its carrier.
  295. The implementation of a rider must have intimate knowledge about the implementation of its carrier, e.g. a file rider must know about the disk sector corresponding to its current position. Thus an extension of a carrier usually requires an appropriate extension of its rider as well. As a consequence, a rider is generated by its carrier, which provides an allocation function for this purpose.
  296. Picture a:  Carrier-Rider Separation showing N:1 Relationship
  297. A rider provides primitive input/output operations, the so-called bottleneck interface. These primitives can be used to build more complex operations which form a higher-level abstraction, possibly even an application-specific abstraction. Such a higher-level abstraction will generally be called a mapper in this documentation. A mapper contains a rider as a link to the carrier. During their use, mappers and riders form 1:1 relations, i.e. pairs.
  298. Picture b:  Carrier-Rider-Mapper Separation
  299. In contrast to riders, mappers know nothing about a carrier's implementation, and thus can be extended independently. This independence means that every mapper may be used on any compatible rider, without the need to implement all combinations of mappers and riders individually. This situation is shown below for the hypothetical case of riders that operate on serial carriers like RS232 links or networks:
  300. Picture c:  Extensibility in two Dimensions
  301. As was explained earlier, Oberon/F generally doesn't allow the extension of concrete types. Mappers are exceptions to this rule, because by their very nature they create or interpret fixed external representations, e.g. number formats on screen or the representation of binary data on a file. Apart from possible optimizations for efficiency reasons, this fixed external representation practically defines the complete behavior of a mapper. Mappers and message records are the only exported concrete types in Oberon/F.
  302. If concrete types are not exported, how can objects of such types be obtained? For this purpose, a module which exports an interface type also provides a so-called directory object, accessible through a global variable. Such a directory provides functions to allocate new objects, and often also procedures for looking up objects by a given key (this property, although not always available, led to the generic term "directory"). For example, a file directory provides functions to create new empty files, as well as to look up existing files by name.
  303. Directory objects are powerful facilities, since they can be replaced at run-time; usually this is done during the boot configuration process. This makes it possible to add and integrate extended services at run-time (i.e. new concrete types), which allows a system to grow in a controlled manner.
  304. To summarize:
  305. - Carrier    container for data
  306. - Rider    access path to carrier, bottleneck interface
  307. - Mapper    creates or interprets external data formats using a rider as link to a carrier
  308. - Directory    facility for generating or for looking up objects of concrete types
  309. User Interaction: Models, Views, and Controllers
  310. Oberon/F supports storable objects, i.e. objects which can be stored to and loaded from non-volatile carriers. Such objects contain arbitrary persistent state (data stored in a non-volatile way, e.g. on disk). For this purpose, a type Store is provided, which is the base type of all storable types. Stores are externalized on and internalized from files. Module Stores provides readers and writers, which are file mappers that implement a binary encoding of Oberon values like characters, integers, sets, or of complete stores. A store uses these mappers to read or write its internal data, which possibly may contain other stores as well, i.e. stores can be embedded hierarchically.
  311. For interactive applications, three different extensions of stores are predefined by Oberon/F: models, views, and controllers. This seperation is known as the MVC separation, originally developed for Smalltalk at Xerox PARC (M for Model, V for View, C for Controller).
  312. Picture a: Model-View-Controller Separation
  313. A Model is a store that contains data which should be presented visually, e.g. a text (sequence of characters plus font attributes), or a graph (set of graphical objects plus pen attributes), etc.
  314. A model is presented by a View. A view maps the model's contents into a rectangular display area, using frames as mappers to ports (e.g. screen ports or printer ports). Frames are also the input channels for mouse and keyboard actions.
  315. Several views may present the same model simultaneously, e.g. there may be two views presenting different parts of the same text, or a list of numbers may be shown as a table and as a bar chart simultaneously.
  316. Views are extended stores, and can thus be embedded in other stores, e.g. other views, or in models. The root of such a hierarchy is called a document. A document corresponds to a file, and is the root object displayed in a window.
  317. A view may also perform user interaction. However, complex applications delegate this task to another object called a Controller. A controller may further delegate the actual operation to an operation, which is an object that specifies an invertable operation on a view or its model, i.e. it supports the undo feature.
  318. Picture b  Document Example
  319. The example above shows the situation where two windows display a document with a text view, where the text contained in this view contains a graphic view. One of the windows is the main window, the other a child window of the same document. Child windows show all or part of a document's view hierarchy. The topmost view in a window is independent of its corresponding views in other windows; i.e. their sizes, scroll positions, and other view attributes may differ. They only share the same model.
  320. If a model contains a view, this view is shared by all views (each in its own window) displaying the same model. However, for each window there exists one frame on this shared view. Thus there may be as many frames on any view as there are windows on this document.
  321. The outlined scheme has the effect that a document is a store hierarchy (not necessarily a tree, but a DAG, i.e. a directed acyclic graph), and that a window is a frame hierarchy (always a tree) which mirrors a subset of the store hierarchy. There are as many frame trees for a store hierarchy as there are windows displaying the same document.
  322. To summarize:
  323. - Store    base type of all storable objects
  324. - Model    data representation
  325. - View    data presentation
  326. - Controller    interaction
  327. - Frame    user input / display output mapper
  328. Message Records
  329. One of the most fundamental language constructs of Oberon are procedures. A procedure combines several statements into one more powerful entity, which can be used as a statement itself, e.g. to form even more powerful procedures.
  330. In order to support object-oriented programming, procedures may be called indirectly via an object. This indirection leads to different actual procedures being called, depending on the object's dynamic type. Procedures which behave in such a polymorphic way are called type-bound procedures (or methods).
  331. With any collection of similar procedures, it is theoretically possible to combine them into one procedure with an additional parameter. This parameter tells which member of the collection is selected when the procedure is called. As an example, consider the following procedures:
  332. PROCEDURE (obj: Object) Show
  333. PROCEDURE (obj: Object) Hide
  334. These procedures can be combined into one procedure
  335. PROCEDURE (obj: Object) Handle (show: BOOLEAN)
  336. This procedure internally selects a Show or Hide behavior based on the additional parameter.
  337. Such a combination becomes problematic, however, if the parameter lists of the different procedures are different, e.g. if the above procedure should be combined with
  338. PROCEDURE (obj: Object) Select (from, to: LONGINT)
  339. A solution to this problem is to pass a record as parameter to the combined procedure. Records are extensible, thus all necessary parameters can be combined in a suitable record extension:
  340.     Message = RECORD END;
  341.     ShowMsg = RECORD (Message) END;
  342.     HideMsg = RECORD (Message) END;
  343.     SelectMsg = RECORD (Message) from, to: LONGINT END;
  344. Such a record can then be passed to a general handler procedure, e.g. a type-bound procedure as in the example below:
  345. PROCEDURE (obj: Object) Handle (VAR msg: Message)
  346. This procedure can not only replace the procedures
  347. PROCEDURE (obj: Object) Show
  348. PROCEDURE (obj: Object) Hide
  349. PROCEDURE (obj: Object) Select (from, to: LONGINT)
  350. but also any other procedure bound to type Object. Internally, Handle uses type tests to determine the action to be taken, and type guards to access the particular parameters (i.e. the record fields of msg). The Oberon WITH statement is a convenient combination of type test and guard:
  351. PROCEDURE (obj: Object) Handle (VAR msg: Message);
  352. BEGIN
  353.     WITH msg: ShowMsg DO
  354.         (* show *)
  355.     | msg: HideMsg DO
  356.         (* hide *)
  357.     | msg: SelectMsg DO
  358.         (* select *)
  359.     ELSE
  360.         (* anything else *)
  361. END Handle;
  362. Such a universal handler procedure is a simple and very general construct, but inconvenient and error-prone. The signature of a handler procedure reveals very little about what the procedure actually does, and the compiler cannot tell if message record fields have not been set up correctly. Thus, Oberon/F generally uses distinct procedures for distinct operations.
  363. However, handler procedures can be convenient under special circumstances, namely when a procedure call must be applied to a large number of objects (broadcast of a message), or when the called object lets some other object do the work for it (forwarding of a message).
  364. In these cases, the number of normal procedures would have to be multiplied, e.g. there would have to be a BroadcastShow procedure in addition to the Show procedure, a BroadcastHide procedure in addition to the Hide procedure, etc. This is inconvenient, because it results in bloated interfaces, and because broadcast procedures typically all work exactly the same way internally, i.e. one Broadcast procedure should be sufficient in principle.
  365. For these reasons, the concept of universal handlers has been used in Oberon/F where this was most appropriate, i.e. where broadcasting or forwarding are used extensively. In the following paragraphs, these places are introduced.
  366. When a user types a key, presses a mouse button, or executes a menu command, these events must be communicated to the appropriate view. Such a view is called a focus view. Views are contained in documents, and documents are displayed in windows. In most user interfaces, one of these windows is the front window (i.e. the focus window), and interactions occur with this window. A window can contain a whole hierarchy of views, thus one of them must be defined as focus.
  367. Events like the ones described above are communicated by sending appropriate controller messages to the focus view (i.e. edit messages, scroll messages, etc.). Theses messages are not sent directly to the focus view, but are passed through the whole view hierarchy until they reach the focus. This makes it possible for a view to filter out (or to perform some other modification on) a message when desired, before it is passed to one of the embedded views.
  368. In fact, Oberon/F does not even know which view currently is the focus. Instead, it sends messages to the focus window's document, from where it is forwarded along the focus path until it reaches the focus view. Every view which receives a controller message can decide on its own whether it wants to forward the message to one of its embedded views, or whether it wants to interpret the message itself. In the latter case, it is the focus view by definition!
  369. Controller messages are forwarded throughout a hierarchy of views, thus they are appropriate candidates for a general forward procedure and for a handler procedure as outlined earlier.
  370. The focus view either handles a controller message itself, or it sends it to a controller object for this purpose. If the message's type is not known, it should simply be ignored, in order to allow for future extensions.
  371. Since in Oberon/F the same document can be shown in several windows simultaneously, every view may be visible several times simultaneously. Thus it is more precise to speak about the focus frame, instead of the focus view. Accordingly, a focus message is sent along the focus window's frame tree, instead of directly along the document's view hierarchy.
  372. Although most interactions are performed with the front window, this is not always the case. Sometimes, two windows are involved in an interaction: a dialog and a document window. As an example, the document window may contain a text view, while a "Find & Replace" dialog may lie in front of the document window. In this case, typing is routed to the dialog, while the find and replace commands operate on the document window.
  373. To support such a situation, Oberon/F defines two foci: a target focus (in the document window) and a front focus (in the dialog box). Every controller message is sent to exactly one of them.
  374. Picture a:  Simplified Example of Focus Hierarchy
  375. When a controller message has arrived at its focus view, it may cause some effect there. A paste message may cause the clipboard's contents to be inserted into the focus view's model, for example. Afterwards, all views which display this same model must be updated, such that they show the new situation correctly. Oberon/F realizes this change propagation by sending a so-called model message to all affected views. This is an example of a message broadcast, and thus the second place where a general message handler is used in Oberon/F, in this case a handler for model messages. Model messages are not forwarded along a specific frame path, but rather sent to all affected views, using a Broadcast procedure.
  376. Unfortunately, there may not only be several views for the same model, but also several frames for the same view, because several windows may be open simultaneously on the same document. This requires that after a model modification, the change propagation must be performed in two steps: from the model to each affected view, and then to each affected frame. This is realized with a second broadcast mechanism, this time for so-called view messages.
  377. Picture b:  Concept of Two-Level Broadcast
  378. All in all, there are only a few places where universal handler procedures and extensible message records are used in Oberon/F, otherwise type-bound procedures are applied.
  379. Garbage Collection as a Prerequisite for Safety
  380. Extensible programs are never frozen, and thus there is no programmer who can know the whole software environment in which his particular piece of software will operate eventually. Software components can be tightly integrated, which means that several extensions may access the same data structure simultaneously, provided that this data structure is exported. Unfortunately, this also means that no programmer can know for sure whether this data structure is also referenced by someone else. This raises the question of when to deallocate a data structure, and by whom.
  381. Many programs contain errors which cause some data structures to remain in memory forever, although they are not referenced anymore (memory leaks). Since memory is only finite, such programs may crash after running for some time.
  382. On the other hand, if memory is deallocated too early, i.e. when it is still being referenced by someone, it may be re-allocated by someone else (dangling pointers). This is even worse than memory leaks, because several variables then use the same memory location to store different values. This is an error which has the character of a time-bomb: its effect may become manifest (often as a violent crash) much later than the error actually occurred, and at a seemingly unrelated place. This lack of locality makes such errors extremely hard to find.
  383. The problem is so severe that it has spawned a whole industry providing tools to deal with it, with limited success.
  384. The problem is hard for closed programs, i.e. programs whose source code is completely accessible to a programmer. But for independently extensible programs ("open systems"), these problems increase exponentially, since the cause of a crash may be any of the loaded extensions, whose internals are not known. For example, a compound document is only as reliable as the weakest component used in this document.
  385. Traditional solutions to such safety issues are not applicable here: usual hardware protection schemes prevent exactly the kind of tight integration which is necessary for compound documents, and which is essential for efficiency.
  386. The root of the problem is that in an open world, no one knows the whole program (since this concept doesn't exist anymore), and thus no one knows when memory can be deallocated safely.
  387. Oberon doesn't have this problem at all: it leaves memory reclamation to a trusted system service, instead of burdening the programmer with explicit deallocation. In essence, such a garbage collector tries to prove for every memory block that it is not being referenced anymore. If the proof succeeds, it gives the block free for future use.
  388. Garbage collection is not a feature of object-orientation. However, for component software it is an indispensible feature.
  389. The Architect's Tools of the Trade
  390. The architecture of a software system is the most difficult thing to get right, and the most expensive thing to get wrong. While code reuse is often discussed and sought after in order to save time and money, it is less obvious that architecture reuse is a much more important issue. If the architecture is flawed, the implementation and maintenance efforts can be dramatically increased, and the life-time of the product can be considerably shortened.
  391. In fact, the purpose of a framework is mainly to define a suitable software architecture in a particular application domain, e.g. editors with graphical user interfaces.
  392. A software architecture should be a structural backbone on which every programmer can rely. If a programming language allows to express architectural decisions explicitly, it becomes a most powerful computer-aided software engineering (CASE) tool, because a compiler for the language can verify the conformance of a program with its underlying architecture. The more it can check at compile-time, the better.
  393. Programming languages differ in the degrees to which their constructs can express architectural decisions. In this respect, Oberon is one of the most powerful languages available today. Oberon provides several facilities to define the static structuring of a program; these facilities are essential tools for the software architect.
  394. The largest structural unit is the compilation unit, a module. Modules define visibility boundaries (for information hiding). What is inside a module (i.e. constants, variables, types, procedures) cannot be seen (i.e. used, called, or modified) outside of this module, except if it is exported explicitly by the module. The exported items of a module are called its interface. A module may import the interfaces of other modules. Modules combine items which together form an abstraction, e.g. the abstraction of a file system. Such an abstraction usually entails not only one abstract data type or class, but a whole ensemble of related object types, as well as constants, global variables, and subroutines.
  395. In Oberon, all software is composed of modules, whether it is an operating system, a framework, an extension component, or a tool. What is considered the operating system is thus not so much a technical issue, but more a matter of convention. Calls between modules are just normal procedure calls, there are no such things as special kernel calls or similar special constructs.
  396. In systems which are not written entirely in Oberon, platform-specific language extensions may be provided, with the sole purpose of interfacing to foreign software, e.g. to the host operating system. Such language extensions must be optimized for the given host system, thus they are not portable and are not considered part of the language proper. Their use is normally limited to a few modules of a program.
  397. The availability of a module construct is one of the major advantages of Oberon compared to most other current object-oriented programming languages.
  398. An Oberon program can be described in terms of its module graph. This is a directed acyclic graph which shows who imports whom. Calls along an edge of such a graph are upcalls, while calls in the opposite direction are normal subroutine calls:
  399. Picture a : Example of a Module Graph
  400. Modules combine items which are related, e.g. several object types which work together to perform some task. The interfaces between modules should be kept as "thin" as possible.
  401. As long as a module's interface (i.e. its exported items, as recorded in a module's symbol file) are not changed, its implementation can be modified freely, without making it necessary to recompile any other modules. A module's interface may even be extended in certain ways (e.g. by adding new procedures) without invalidating existing code. This is possible due to advanced compiler techniques which are applied in modern Oberon compilers.
  402. Note: sometimes the problem of changing the implementation of a module without invalidating other code is called the "fragile base class problem". In the context of this text, we refer to it as the "syntactic fragile base class problem", in order to distinguish it from the more difficult "semantic fragile base class problem" described in the next section.
  403. The second important static structuring facility of Oberon is its type system. Every variable has a type, which defines the set of values that the variable may contain, and the operations which are legal on this variable. Besides several basic types such as characters, integers, or sets, Oberon provides two kinds of structured types, namely arrays and records. Arrays are vectors of values with the same type, while records are tuples of values which may have different types. An array element is accessed via an index, while a record field is accessed via a name. Array elements and record fields may be records, arrays, or variables themselves. This makes it possible to combine types in a hierarchical way. Pointer types and procedure types are further powerful type constructs.
  404. In order to support component-oriented programming, Oberon provides record extension and type-bound procedures. Record extension allows to declare one record type (sometimes called a class) as an extension of another record type. The extending type is compatible to the extended type (base type), which means that wherever a variable of the base type can be used, a variable of the extension type may be used as well. An extended type "is a" base type, but not vice versa.
  405. A pointer type declares the record type to which it is bound, this is the so-called static type. However, the static type of a variable only defines a minimal requirement for the contents of this variable. Any extension of the static type can be assigned to the variable as well. Its true type is called the dynamic type of the pointer variable. The dynamic type is defined when a record is allocated, it cannot be changed afterwards.
  406. Using record extension and pointers, polymorphic data structures can be built. An example of a polymorphic data structure is the list of windows managed by a window system: the list may contain document windows as well as dialogs. Both may be declared as extensions of a basic window type.
  407. A type-bound procedure (sometimes called a method) is a procedure that is bound to a pointer (or its record) type. This means that depending on the pointer variable's dynamic type, a different procedure may actually be called. As a result, a pointer variable - which can appropriately be called an object now - may behave differently, depending on its dynamic type. Let us suppose that a window pointer has a procedure Close bound to it. Then a dialog box (which is an extended window) may simply close the window when this procedure is called, while a document window may first ask whether its contents should be stored on disk before closing.
  408. Strong typing, strong modularity, and the integration of object-oriented programming techniques into a proven type system are among the important advantages of the Oberon language, making it not only an implementer's tool, but also a fundamental tool for the software architect.
  409. From White-Box Frameworks Towards Black-Box Frameworks
  410. In Oberon, a procedure may be bound to a record or pointer type. When a record type is extended, the extending type inherits all procedures bound to the base type. However, every inherited procedure may be overridden, i.e. the procedure's implementation may be redefined.
  411. Overriding is a powerful feature, since it allows the replacement or modification of inherited (default) behavior. However, strict rules must be followed in order to use overriding correctly. Incorrect use leads to the so-called fragile base class problem, where the behavior of a base type cannot be modified anymore without breaking some of its extension types.
  412. Note: we call this the "semantic fragile base class problem" in contrast to the less difficult "syntactic fragile base class problem" described in the previous section.
  413. A general rule for a correct use of overriding is the following:
  414. An overriding procedure must be compatible to the overridden procedure both syntactically and semantically.
  415. Syntactic compatibility can be expressed in the language and checked by the compiler. Alas, semantic compatibility is more difficult to achieve. This is the cause of many design errors in class libraries.
  416. Instead of using the term "overriding", it is much less misleading to use the term "extending". A procedure does not override or redefine another procedure, it much rather extends it, i.e. it does everything the base procedure does, plus something more. This immediately lets us conclude that the base procedure should be called in the extending procedure, with a super call. This guarantees that whatever the base procedure does, will also be done by the extending procedure, and in exactly the same way. Which is something we can expect of a semantically compatible procedure.
  417. There is a special case where we don't want to call a base procedure in an extended procedure, namely when the base procedure provides a default behavior which can be re-implemented in a more efficient way. As an example, consider a file object which provides two procedures, one for writing a single byte, and another one for writing an array of bytes. Obviously, the second procedure can be implemented in terms of the first one. This may be reasonable to do as a default behavior. A particular file implementation however, e.g. for disk files, may increase performance considerably by re-implementing the same functionality directly.
  418. If a base procedure is designed as a default procedure, its behavior must be specified completely (no hidden behavior), and everything needed for a re-implementation must be exported, i.e. must be available to possible re-implementers.
  419. A special case of a default procedure is the empty procedure. For efficiency, an empty procedure should not be called by an extending procedure via a super call. Empty procedures are called hook procedures.
  420. An empty procedure can be useful, because it forms part of its object's interface. If this is the only function of an empty procedure, we call it an interface procedure. An interface procedure is a procedure definition without a corresponding implementation, because the implementation cannot be provided where the interface is defined. The implementation must be delivered by the extensions instead. A procedure which is not an interface procedure is called a concrete procedure.
  421. A data type which provides one or more interface procedures is called an interface type. Interface types are only interfaces, they have missing or at least incomplete implementations, and thus no variable of such a type can ever be used. Interface procedures must be overridden in any concrete extension type, and such a concrete type (i.e. a direct extension of an interface type) must not perform a super call. In Oberon/F, there is a convention to call the HALT statement in the body of an interface procedure, to detect any incorrect use.
  422. Only variables of concrete types may be allocated and used. It is the programmer's responsibility to only allocate variables of concrete types.
  423. Extending concrete types is dangerous, because a concrete type has much more specialized semantics than an interface type. This means that extensions of concrete types are likely to have changed (i.e. incompatible) semantics, instead of extended semantics as would be proper. In particular, complicated calling sequences can occur (upcalls, downcalls, super calls), resulting in very complex interactions of the side effects caused by these procedures. There is no way to specify (and thus to fully understand) such side effect interactions, except to publish all source code.
  424. Frameworks which can only be used given their whole source code are called white-box frameworks. Experience shows that white-box frameworks suffer from two fundamental problems:
  425. The first one is complexity: since interfaces cannot be separated from implementations in a white-box framework, the whole framework's implementation must be studied and understood by a programmer. This becomes virtually impossible for large and complex frameworks.
  426. The second problem with the lacking distinction between interface and implementation is over-specification: every implementation detail must be considered part of the framework's specification, and thus cannot be changed anymore: the fragile base class problem mentioned earlier.
  427. Let us summarize the problem with the extension of concrete types ("code inheritance"): A concrete procedure may internally execute statements, such as assignments or procedure calls, which modify the program's state. The exact nature and sequence of such modifications determine the behavior of the whole program, not only of the module or object to which the procedure belongs. In the presence of overriding and super calls, there exists no notation which allows to specify such effects precisely enough, except the program's complete source code. A program component whose complete source code has been published may not be modified anymore, since this might break existing extensions.
  428. Since this was felt to be too restrictive, Oberon/F was designed more as a black-box framework instead: In general, concrete types cannot be extended in Oberon/F. This is achieved simply by not exporting them. Yet, type exension of interface types is used extensively, since this doesn't pose the described problems.
  429. Abandonning code inheritance does not mean abandonning code reuse: Instead of reuse by inheritance, Oberon/F uses reuse by composition, e.g. a document is composed of views.
  430. TextControllers.StdCtrlDesc
  431. TextControllers.ControllerDesc
  432. Containers.ControllerDesc
  433. Controllers.ControllerDesc
  434. Helvetica
  435. Documents.ControllerDesc
  436.